package org.hrqing.chess.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.annotation.PostConstruct;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.hrqing.chess.config.props.EngineConfig;
import org.hrqing.chess.config.props.SystemConfigProps;
import org.hrqing.chess.entity.Log;
import org.hrqing.chess.entity.LogPiece;
import org.hrqing.chess.util.JSON;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Hrqing
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PikaFishService {

    private static final String[][] LOCATION_X = new String[][]{
            new String[]{"1", "a"},
            new String[]{"2", "b"},
            new String[]{"3", "c"},
            new String[]{"4", "d"},
            new String[]{"5", "e"},
            new String[]{"6", "f"},
            new String[]{"7", "g"},
            new String[]{"8", "h"},
            new String[]{"9", "i"},
    };

    private static final String[][] LOCATION_Y = new String[][]{
            new String[]{"1", "9"},
            new String[]{"2", "8"},
            new String[]{"3", "7"},
            new String[]{"4", "6"},
            new String[]{"5", "5"},
            new String[]{"6", "4"},
            new String[]{"7", "3"},
            new String[]{"8", "2"},
            new String[]{"9", "1"},
            new String[]{"10", "0"},
    };

    public static MoveResult coordinate(String move) {
        MoveResult result = new MoveResult();
        String substringS = move.substring(0, 2);
        String sx = substringS.substring(0, 1);
        for (String[] locationX : LOCATION_X) {
            if (locationX[1].equals(sx)) {
                sx = locationX[0];
                break;
            }
        }
        result.setMsx(Integer.parseInt(sx));
        String sy = substringS.substring(1);
        for (String[] locationY : LOCATION_Y) {
            if (locationY[1].equals(sy)) {
                sy = locationY[0];
                break;
            }
        }
        result.setMsy(Integer.parseInt(sy));
        String substringE = move.substring(2);
        String ex = substringE.substring(0, 1);
        for (String[] locationX : LOCATION_X) {
            if (locationX[1].equals(ex)) {
                ex = locationX[0];
                break;
            }
        }
        result.setMex(Integer.parseInt(ex));
        String ey = substringE.substring(1);
        for (String[] locationY : LOCATION_Y) {
            if (locationY[1].equals(ey)) {
                ey = locationY[0];
                break;
            }
        }
        result.setMey(Integer.parseInt(ey));
        return result;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class MoveResult {
        private int msx;
        private int msy;
        private int mex;
        private int mey;
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class AnalysisResult {
        private String bestMove;
        private Integer score;
        private Long nodes;
        private Long nps;
        private Long depth;
        private String pv;
        private MoveResult move;
    }

    private final EngineConfig config;

    private Process process;
    private BufferedWriter writer;
    private BufferedReader reader;

    @PostConstruct
    public void initEngine() throws IOException {
        String nnuePath = config.getEvalFile();
        String os = System.getProperty("os.name").toLowerCase();
        String path = config.getEnginePath()
                .replace("{os}", os.contains("win")  ? "windows" :
                        os.contains("mac")  ? "macos" : "linux");
        Path fileName = new File(path).toPath().getFileName();

        Path fixedDir = Paths.get(System.getProperty("java.io.tmpdir"),  "pikafish_work");
        // 启动时清理旧文件
        if (Files.exists(fixedDir))  {
            Files.walk(fixedDir)
                    .sorted(Comparator.reverseOrder())
                    .forEach(each -> {
                        try { Files.deleteIfExists(each);  }
                        catch (IOException e) {
                            log.error(" 删除失败：{} ", each, e);
                        }
                    });
        }
        Files.createDirectories(fixedDir);

        Path targetEngine = fixedDir.resolve(fileName.getFileName());
        try (InputStream is = getClass().getResourceAsStream(path))  {
            Files.copy(is,
                    targetEngine,
                    StandardCopyOption.REPLACE_EXISTING);
        } catch (FileSystemNotFoundException e) {
            log.error(" 文件不存在", e);
        }

        Path nnueTargetEngine = fixedDir.resolve(new File(nnuePath).toPath().getFileName());
        try (InputStream is = getClass().getResourceAsStream(nnuePath))  {
            Files.copy(is,
                    nnueTargetEngine,
                    StandardCopyOption.REPLACE_EXISTING);
        } catch (FileSystemNotFoundException e) {
            log.error(" 文件不存在", e);
        }

        // 加载二进制文件
        if (!targetEngine.toFile().setExecutable(true))  {
            throw new RuntimeException("引擎文件权限设置失败");
        }

        // 启动引擎进程
        process = new ProcessBuilder(targetEngine.toAbsolutePath().toString()).start();

        writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
        reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

        // 初始化引擎参数
        sendCommand("uci");
        sendCommand("setoption name EvalFile value " + nnueTargetEngine.toAbsolutePath().toString());
        sendCommand("setoption name Threads value " + config.getThreads());
        sendCommand("setoption name Hash value " + config.getHash());
    }

    public AnalysisResult logAnalyze(SystemConfigProps.Depth depth, List<LogPiece> pieces) throws IOException {
        pieces.sort((o1, o2) -> {
            Integer y1 = o1.getY();
            Integer y2 = o2.getY();
            return y1.compareTo(y2);
        });
        List<String> commands = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            AtomicInteger iatomicInteger = new AtomicInteger(i);
            List<LogPiece> list = new ArrayList<>(pieces.stream().filter(filter -> filter.getY() == iatomicInteger.get()).toList());
            if (list.isEmpty()) {
                commands.add("9");
            }else {
                list.sort((o1, o2) -> {
                    Integer x1 = o1.getX();
                    Integer x2 = o2.getX();
                    return x1.compareTo(x2);
                });
                StringBuilder builder = new StringBuilder();
                for (int j = 1; j < 10; j++) {
                    AtomicInteger jatomicInteger = new AtomicInteger(j);
                    Optional<LogPiece> first = list.stream().filter(filter -> filter.getX() == jatomicInteger.get()).findFirst();
                    if (first.isEmpty()) {
                        builder.append("#");
                    }else {
                        builder.append(first.get().getSign());
                    }
                }
                commands.add(format(builder.toString()));
            }
        }
        String command = String.join("/", commands);
        command = command + " b";
        command = command + " - - -";
        command = command + " 0 1";

        log.info("command: {}", command);
        AnalysisResult result = analyze(command, depth.getDepth());
        MoveResult coordinate = coordinate(result.getBestMove());
        result.setMove(coordinate);
        return result;
    }

    private static String format(String value) {
        StringBuilder result = new StringBuilder();
        int hashCount = 0;

        for (char ch : value.toCharArray())  {
            if (ch == '#') {
                hashCount++;
            } else {
                // 字母或其它字符处理
                if (Character.isLetter(ch))  {
                    if (hashCount > 0) {
                        result.append(hashCount);
                    }
                    result.append(ch);
                    hashCount = 0;
                }
            }
        }
        // 处理末尾未闭合的
        if (hashCount > 0) {
            result.append(hashCount);
        }
        return result.toString();
    }

    /**
     * rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - - 0 1
     * [布局] [回合方] [车炮状态] [将帅位置] [未过河兵位] [半回合计数] [总回合数]
     */
    public AnalysisResult analyze(String fen, int depth) throws IOException {
        sendCommand("position fen " + fen);
        sendCommand("go depth " + depth);

        StringBuilder output = new StringBuilder();
        long startTime = System.currentTimeMillis();

        // 异步读取引擎输出
        new Thread(() -> {
            try {
                String line;
                while ((line = reader.readLine())  != null) {
                    output.append(line).append("\n");
                    if (line.startsWith("bestmove")) {
                        break;
                    }
                }
            } catch (IOException e) {
                log.error(" 引擎输出读取异常", e);
            }
        }).start();

        // 超时控制
        while (System.currentTimeMillis() - startTime < config.getTimeout())  {
            if (output.toString().contains("bestmove"))  {
                return parseResult(output.toString());
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
        }
        throw new RuntimeException("引擎响应超时");
    }

    public boolean isEngineAlive() {
        return process != null && process.isAlive();
    }

    private AnalysisResult parseResult(String output) throws JsonProcessingException {
        String replace = output.replace("\n\n", "\n");
        List<String> outs = new ArrayList<>(Arrays.asList(replace.split("\n")));
        List<String> depthList = outs.stream().filter(filter -> filter != null && filter.startsWith("info depth")).toList();
        String depth = depthList.getLast();
        List<String> bestmoveList = outs.stream().filter(filter -> filter != null && filter.startsWith("bestmove")).toList();
        String bestmove = bestmoveList.getLast();
        return depth(depth, bestmove);
    }

    private static AnalysisResult depth(String depthText, String bestText) {
        AnalysisResult result = new AnalysisResult();
        Pattern depthPattern = Pattern.compile("info\\s+depth\\s+(\\d+)");
        String depthResult = pattern(depthText, depthPattern);
        result.setDepth(parseLong(depthResult));

        Pattern scorePattern = Pattern.compile("score\\s+cp\\s+(-?\\d+)");
        String scoreResult = pattern(depthText, scorePattern);
        result.setScore(parseInt(scoreResult));

        Pattern nodesPattern = Pattern.compile("nodes\\s+(\\d+)");
        String nodesResult = pattern(depthText, nodesPattern);
        result.setNodes(parseLong(nodesResult));

        Pattern npsPattern = Pattern.compile("nps\\s+(\\d+)");
        String npsResult = pattern(depthText, npsPattern);
        result.setNps(parseLong(npsResult));

        Pattern pvPattern = Pattern.compile("pv\\s+([a-z0-9]{4}(?:\\s+[a-z0-9]{4})*)");
        String pvResult = pattern(depthText, pvPattern);
        result.setPv(pvResult);

        Pattern bestPattern = Pattern.compile("bestmove\\s+(\\w+)");
        String bestResult = pattern(bestText, bestPattern);
        result.setBestMove(bestResult);
        return result;
    }

    private static Long parseLong(String text) {
        Integer integer = parseInt(text);
        return integer == null ? null : Long.valueOf(integer);
    }

    private static Integer parseInt(String text) {
        if (text == null) {
            return null;
        }
        try {
            return Integer.parseInt(text);
        }catch (Exception ignored) {
        }
        return null;
    }

    private static String pattern(String text, Pattern pattern) {
        Matcher matcher = pattern.matcher(text);
        try {
            while (matcher.find())  {
                return matcher.group(1);
            }
        }catch (Exception ignored) {
        }
        return null;
    }


    private void sendCommand(String cmd) throws IOException {
        writer.write(cmd  + "\n");
        writer.flush();
    }

}
